import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
import time
from datetime import datetime
from player import AudioInputSimulator
try:
    from pynput import keyboard
    PYNPUT_AVAILABLE = True
except ImportError:
    PYNPUT_AVAILABLE = False
    print("警告: pynput 库未安装，全局热键功能将不可用")
    print("请运行: pip install pynput")

class ToolTip:
    """工具提示类"""
    def __init__(self, widget, text):
        self.widget = widget
        self.text = text
        self.tooltip = None
        self.widget.bind("<Enter>", self.on_enter)
        self.widget.bind("<Leave>", self.on_leave)
    
    def on_enter(self, event=None):
        if self.tooltip:
            return
        x, y, _, _ = self.widget.bbox("insert") if hasattr(self.widget, 'bbox') else (0, 0, 0, 0)
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + 25
        
        self.tooltip = tk.Toplevel(self.widget)
        self.tooltip.wm_overrideredirect(True)
        self.tooltip.wm_geometry(f"+{x}+{y}")
        
        label = tk.Label(self.tooltip, text=self.text, 
                        background="lightyellow", 
                        relief="solid", borderwidth=1,
                        font=("Arial", 9))
        label.pack()
    
    def on_leave(self, event=None):
        if self.tooltip:
            self.tooltip.destroy()
            self.tooltip = None

class AudioHotkeyGUI:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("音频热键播放器")
        self.root.geometry("1200x700")
        self.root.minsize(800, 600)
        
        # 设置主题样式
        self.style = ttk.Style()
        self.style.theme_use('clam')
        
        # 音频模拟器实例
        self.audio_simulator = AudioInputSimulator()
        
        # 热键映射 {键: 文件路径}
        self.hotkey_mappings = {}
        
        # 当前选中的输出设备
        self.current_device_index = None
        
        # 热键监听器
        self.hotkey_listener = None
        self.is_listening = False
        
        # 当前播放状态
        self.is_playing = False
        
        # 播放历史记录
        self.play_history = []
        
        self.setup_ui()
        self.refresh_audio_devices()
        
    def setup_ui(self):
        """设置用户界面"""
        # 配置根窗口的网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        
        # 主容器框架
        main_container = ttk.Frame(self.root, padding="10")
        main_container.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        main_container.columnconfigure(0, weight=2)  # 左侧权重更大
        main_container.columnconfigure(1, weight=1)  # 右侧
        main_container.rowconfigure(0, weight=1)
        
        # 创建左侧和右侧面板
        self.setup_left_panel(main_container)
        self.setup_right_panel(main_container)
        
        # 状态栏（跨两列）
        status_frame = ttk.Frame(main_container)
        status_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
        status_frame.columnconfigure(0, weight=1)
        
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_label = ttk.Label(status_frame, textvariable=self.status_var, 
                                relief=tk.SUNKEN, anchor=tk.W, padding="5")
        status_label.grid(row=0, column=0, sticky=(tk.W, tk.E))
        
        # 默认添加几个热键行
        self.hotkey_rows = []
        for key in ['i', 'o', 'p']:
            self.add_hotkey_row(key)
    
    def setup_left_panel(self, parent):
        """设置左侧控制面板"""
        left_frame = ttk.Frame(parent, padding="5")
        left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
        left_frame.columnconfigure(0, weight=1)
        left_frame.rowconfigure(2, weight=1)
        
        # 标题
        title_label = ttk.Label(left_frame, text="音频热键播放器", 
                               font=("Arial", 16, "bold"))
        title_label.grid(row=0, column=0, pady=(0, 15))
        
        # 设备选择区域
        self.setup_device_section(left_frame)
        
        # 热键配置区域
        self.setup_hotkey_section(left_frame)
        
        # 控制区域
        self.setup_control_section(left_frame)
    
    def setup_device_section(self, parent):
        """设置设备选择区域"""
        device_frame = ttk.LabelFrame(parent, text="音频设备设置", padding="10")
        device_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
        device_frame.columnconfigure(1, weight=1)
        
        ttk.Label(device_frame, text="输出设备:").grid(row=0, column=0, 
                                                    sticky=tk.W, padx=(0, 10))
        
        self.device_var = tk.StringVar()
        self.device_combo = ttk.Combobox(device_frame, textvariable=self.device_var, 
                                        state="readonly", width=40)
        self.device_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        self.device_combo.bind("<<ComboboxSelected>>", self.on_device_changed)
        
        refresh_btn = ttk.Button(device_frame, text="刷新设备", 
                                command=self.refresh_audio_devices)
        refresh_btn.grid(row=0, column=2)
    
    def setup_hotkey_section(self, parent):
        """设置热键配置区域"""
        hotkey_frame = ttk.LabelFrame(parent, text="热键配置", padding="10")
        hotkey_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        hotkey_frame.columnconfigure(0, weight=1)
        hotkey_frame.rowconfigure(1, weight=1)
        
        # 表头
        header_frame = ttk.Frame(hotkey_frame)
        header_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
        header_frame.columnconfigure(1, weight=1)
        
        ttk.Label(header_frame, text="热键", font=("Arial", 9, "bold")).grid(
            row=0, column=0, padx=(0, 10), sticky=tk.W)
        ttk.Label(header_frame, text="音频文件", font=("Arial", 9, "bold")).grid(
            row=0, column=1, sticky=tk.W)
        ttk.Label(header_frame, text="操作", font=("Arial", 9, "bold")).grid(
            row=0, column=2, padx=(10, 0), sticky=tk.W)
        
        # 滚动区域
        scroll_frame = ttk.Frame(hotkey_frame)
        scroll_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scroll_frame.columnconfigure(0, weight=1)
        scroll_frame.rowconfigure(0, weight=1)
        
        canvas = tk.Canvas(scroll_frame, height=200)
        scrollbar = ttk.Scrollbar(scroll_frame, orient="vertical", command=canvas.yview)
        self.scrollable_frame = ttk.Frame(canvas)
        
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        
        canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        
        canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        
        # 配置滚动区域的列权重
        self.scrollable_frame.columnconfigure(1, weight=1)
        
        # 添加热键按钮
        add_frame = ttk.Frame(hotkey_frame)
        add_frame.grid(row=2, column=0, sticky=tk.W, pady=(10, 0))
        
        ttk.Button(add_frame, text="+ 添加热键", 
                  command=self.add_hotkey_row).pack(side=tk.LEFT)
    
    def setup_control_section(self, parent):
        """设置控制区域"""
        control_frame = ttk.LabelFrame(parent, text="控制", padding="10")
        control_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
        
        button_frame = ttk.Frame(control_frame)
        button_frame.pack(fill=tk.X)
        
        self.start_btn = ttk.Button(button_frame, text="开始监听热键", 
                                   command=self.toggle_hotkey_listening)
        self.start_btn.pack(side=tk.LEFT, padx=(0, 10))
        
        self.stop_btn = ttk.Button(button_frame, text="停止播放", 
                                  command=self.stop_audio, state="disabled")
        self.stop_btn.pack(side=tk.LEFT)
    
    def setup_right_panel(self, parent):
        """设置右侧播放记录面板"""
        right_frame = ttk.LabelFrame(parent, text="播放记录", padding="10")
        right_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 0))
        right_frame.columnconfigure(0, weight=1)
        right_frame.rowconfigure(1, weight=1)
        
        # 记录操作按钮
        record_control_frame = ttk.Frame(right_frame)
        record_control_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
        
        ttk.Button(record_control_frame, text="清空记录", 
                  command=self.clear_play_history).pack(side=tk.LEFT)
        
        # 播放记录列表
        record_frame = ttk.Frame(right_frame)
        record_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        record_frame.columnconfigure(0, weight=1)
        record_frame.rowconfigure(0, weight=1)
        
        # 创建Treeview来显示播放记录
        columns = ("时间", "热键", "文件名", "状态")
        self.history_tree = ttk.Treeview(record_frame, columns=columns, show="headings", height=15)
        
        # 设置列
        self.history_tree.heading("时间", text="时间")
        self.history_tree.heading("热键", text="热键")
        self.history_tree.heading("文件名", text="文件名")
        self.history_tree.heading("状态", text="状态")
        
        # 设置列宽
        self.history_tree.column("时间", width=80, minwidth=60)
        self.history_tree.column("热键", width=50, minwidth=30)
        self.history_tree.column("文件名", width=150, minwidth=100)
        self.history_tree.column("状态", width=60, minwidth=50)
        
        # 添加滚动条
        history_scrollbar = ttk.Scrollbar(record_frame, orient="vertical", 
                                         command=self.history_tree.yview)
        self.history_tree.configure(yscrollcommand=history_scrollbar.set)
        
        self.history_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        history_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
    
    def truncate_text(self, text, max_length=40):
        """截断文本并添加省略号"""
        if len(text) <= max_length:
            return text
        return text[:max_length-3] + "..."
    
    def refresh_audio_devices(self):
        """刷新音频设备列表"""
        try:
            # 获取设备列表
            devices = []
            device_indices = []
            full_device_names = []  # 保存完整设备名称用于工具提示
            
            info = self.audio_simulator.p.get_host_api_info_by_index(0)
            num_devices = info.get('deviceCount', 0)
            
            if not isinstance(num_devices, int):
                num_devices = 0
            
            for i in range(num_devices):
                try:
                    device_info = self.audio_simulator.p.get_device_info_by_host_api_device_index(0, i)
                    max_output_channels = device_info.get('maxOutputChannels', 0)
                    if isinstance(max_output_channels, (int, float)) and max_output_channels > 0:
                        device_name = device_info.get('name', f'设备 {i}')
                        full_name = f"ID {i} - {device_name}"
                        display_name = self.truncate_text(full_name, 50)
                        
                        devices.append(display_name)
                        device_indices.append(i)
                        full_device_names.append(full_name)
                except Exception:
                    continue
            
            self.device_combo['values'] = devices
            self.device_indices = device_indices
            self.full_device_names = full_device_names
            
            # 为设备下拉框添加工具提示
            if hasattr(self, 'device_tooltip'):
                self.device_tooltip = None  # 清除旧的工具提示
            
            # 自动选择合适的设备
            if devices:
                # 尝试找到虚拟音频设备
                default_index = self.audio_simulator.find_output_device()
                for idx, device_idx in enumerate(device_indices):
                    if device_idx == default_index:
                        self.device_combo.current(idx)
                        self.current_device_index = default_index
                        # 为当前选中的设备添加工具提示
                        if idx < len(full_device_names):
                            self.device_tooltip = ToolTip(self.device_combo, full_device_names[idx])
                        break
                else:
                    self.device_combo.current(0)
                    self.current_device_index = device_indices[0] if device_indices else None
                    if full_device_names:
                        self.device_tooltip = ToolTip(self.device_combo, full_device_names[0])
            
            self.status_var.set(f"找到 {len(devices)} 个输出设备")
            
        except Exception as e:
            messagebox.showerror("错误", f"刷新设备列表失败: {e}")
    
    def on_device_changed(self, event=None):
        """设备选择改变时的回调"""
        try:
            current_selection = self.device_combo.current()
            if current_selection >= 0 and current_selection < len(self.device_indices):
                self.current_device_index = self.device_indices[current_selection]
                # 更新工具提示
                if hasattr(self, 'device_tooltip'):
                    self.device_tooltip = None
                if hasattr(self, 'full_device_names') and current_selection < len(self.full_device_names):
                    self.device_tooltip = ToolTip(self.device_combo, self.full_device_names[current_selection])
                
                display_name = self.device_combo.get()
                self.status_var.set(f"已选择设备: {display_name}")
        except Exception as e:
            messagebox.showerror("错误", f"设备选择失败: {e}")
    
    def add_to_play_history(self, hotkey, file_path, status="成功"):
        """添加播放记录"""
        try:
            timestamp = datetime.now().strftime("%H:%M:%S")
            filename = file_path.split('/')[-1].split('\\')[-1]  # 提取文件名
            filename = self.truncate_text(filename, 20)  # 限制文件名长度
            
            # 插入到树视图的顶部
            self.history_tree.insert("", 0, values=(timestamp, hotkey, filename, status))
            
            # 限制历史记录数量，只保留最新的100条
            items = self.history_tree.get_children()
            if len(items) > 100:
                self.history_tree.delete(items[-1])
            
            # 将记录添加到内部列表
            self.play_history.insert(0, {
                'time': timestamp,
                'hotkey': hotkey,
                'file': file_path,
                'filename': filename,
                'status': status
            })
            
            # 限制内部列表长度
            if len(self.play_history) > 100:
                self.play_history.pop()
                
        except Exception as e:
            print(f"添加播放记录失败: {e}")
    
    def clear_play_history(self):
        """清空播放记录"""
        try:
            # 清空树视图
            for item in self.history_tree.get_children():
                self.history_tree.delete(item)
            
            # 清空内部列表
            self.play_history.clear()
            
            self.status_var.set("播放记录已清空")
        except Exception as e:
            messagebox.showerror("错误", f"清空记录失败: {e}")
    
    def add_hotkey_row(self, key=''):
        """添加热键配置行"""
        row_num = len(self.hotkey_rows)
        
        # 热键输入
        key_var = tk.StringVar(value=key)
        key_entry = ttk.Entry(self.scrollable_frame, textvariable=key_var, width=8)
        key_entry.grid(row=row_num, column=0, padx=(0, 10), pady=3, sticky=tk.W)
        
        # 文件路径显示容器
        file_frame = ttk.Frame(self.scrollable_frame)
        file_frame.grid(row=row_num, column=1, padx=(0, 10), pady=3, sticky=(tk.W, tk.E))
        file_frame.columnconfigure(0, weight=1)
        
        # 文件路径显示
        file_var = tk.StringVar()
        file_label = ttk.Label(file_frame, textvariable=file_var, anchor='w', 
                              relief=tk.SUNKEN, padding="3")
        file_label.grid(row=0, column=0, sticky=(tk.W, tk.E))
        
        # 选择文件按钮
        def select_file():
            filename = filedialog.askopenfilename(
                title="选择音频文件",
                filetypes=[
                    ("音频文件", "*.wav *.mp3 *.flac *.ogg *.aiff"),
                    ("WAV文件", "*.wav"),
                    ("MP3文件", "*.mp3"),
                    ("所有文件", "*.*")
                ]
            )
            if filename:
                # 显示截断的文件路径
                display_path = self.truncate_text(filename, 40)
                file_var.set(display_path)
                
                # 添加工具提示显示完整路径
                if hasattr(file_label, 'tooltip'):
                    file_label.tooltip = None
                file_label.tooltip = ToolTip(file_label, filename)
                
                # 更新热键映射
                current_key = key_var.get().strip().lower()
                if current_key:
                    self.hotkey_mappings[current_key] = filename
        
        file_btn = ttk.Button(file_frame, text="浏览", command=select_file, width=8)
        file_btn.grid(row=0, column=1, padx=(5, 0))
        
        # 删除按钮
        def delete_row():
            current_key = key_var.get().strip().lower()
            if current_key in self.hotkey_mappings:
                del self.hotkey_mappings[current_key]
            
            # 删除UI元素
            key_entry.destroy()
            file_frame.destroy()
            del_btn.destroy()
            
            # 从列表中移除
            row_data = (key_var, file_var, key_entry, file_frame, file_btn, del_btn)
            if row_data in self.hotkey_rows:
                self.hotkey_rows.remove(row_data)
        
        del_btn = ttk.Button(self.scrollable_frame, text="删除", command=delete_row, width=8)
        del_btn.grid(row=row_num, column=2, padx=(10, 0), pady=3)
        
        # 绑定热键更新事件
        def update_hotkey(event=None):
            old_key = getattr(update_hotkey, 'old_key', '')
            current_key = key_var.get().strip().lower()
            
            if old_key and old_key in self.hotkey_mappings:
                del self.hotkey_mappings[old_key]
            
            if current_key and current_key in self.hotkey_mappings:
                # 如果新键已存在，保持原有映射
                pass
            elif current_key and file_var.get():
                # 获取完整文件路径
                full_path = None
                for existing_key, path in self.hotkey_mappings.items():
                    if path.endswith(file_var.get()) or file_var.get() in path:
                        full_path = path
                        break
                
                if full_path:
                    self.hotkey_mappings[current_key] = full_path
            
            update_hotkey.old_key = current_key
        
        update_hotkey.old_key = key.lower() if key else ''
        key_var.trace('w', lambda *args: update_hotkey())
        
        # 保存行数据
        row_data = (key_var, file_var, key_entry, file_frame, file_btn, del_btn)
        self.hotkey_rows.append(row_data)
    
    def toggle_hotkey_listening(self):
        """切换热键监听状态"""
        if not PYNPUT_AVAILABLE:
            messagebox.showerror("错误", "pynput 库未安装，无法使用全局热键功能\n请运行: pip install pynput")
            return
        
        if not self.is_listening:
            self.start_hotkey_listening()
        else:
            self.stop_hotkey_listening()
    
    def start_hotkey_listening(self):
        """开始热键监听"""
        if not self.hotkey_mappings:
            messagebox.showwarning("警告", "请至少配置一个热键和音频文件")
            return
        
        try:
            self.is_listening = True
            self.start_btn.config(text="停止监听热键")
            self.status_var.set("正在监听热键...")
            
            def on_key_press(key):
                try:
                    if hasattr(key, 'char') and key.char:
                        pressed_key = key.char.lower()
                        if pressed_key in self.hotkey_mappings:
                            file_path = self.hotkey_mappings[pressed_key]
                            self.play_audio(file_path, pressed_key)
                except AttributeError:
                    pass
            
            self.hotkey_listener = keyboard.Listener(on_press=on_key_press)
            self.hotkey_listener.start()
            
        except Exception as e:
            self.is_listening = False
            self.start_btn.config(text="开始监听热键")
            messagebox.showerror("错误", f"启动热键监听失败: {e}")
    
    def stop_hotkey_listening(self):
        """停止热键监听"""
        self.is_listening = False
        self.start_btn.config(text="开始监听热键")
        
        if self.hotkey_listener:
            self.hotkey_listener.stop()
            self.hotkey_listener = None
        
        self.status_var.set("已停止监听热键")
    
    def play_audio(self, file_path, key):
        """播放音频文件"""
        if self.current_device_index is None:
            self.status_var.set("错误: 未选择输出设备")
            self.add_to_play_history(key, file_path, "失败-无设备")
            return
        
        try:
            # 停止当前播放
            if self.is_playing:
                self.audio_simulator.stop()
            
            # 在新线程中播放音频
            def play_thread():
                self.is_playing = True
                self.stop_btn.config(state="normal")
                
                # 显示文件名而不是完整路径
                filename = file_path.split('/')[-1].split('\\')[-1]
                display_name = self.truncate_text(filename, 30)
                self.status_var.set(f"播放: {key} -> {display_name}")
                
                try:
                    success = self.audio_simulator.play_audio_to_virtual_mic(
                        file_path, 
                        device_index=self.current_device_index,
                        loop=False
                    )
                    
                    if not success:
                        self.status_var.set(f"播放失败: {display_name}")
                        # 在主线程中添加播放记录
                        self.root.after(0, lambda: self.add_to_play_history(key, file_path, "失败"))
                    else:
                        # 添加成功播放记录
                        self.root.after(0, lambda: self.add_to_play_history(key, file_path, "成功"))
                        
                        # 等待播放完成
                        while self.audio_simulator.is_playing and self.is_playing:
                            time.sleep(0.1)
                        
                        # 播放完成后更新记录状态
                        if self.is_playing:  # 正常播放完成
                            self.root.after(0, lambda: self.update_last_history_status("完成"))
                        else:  # 被中断
                            self.root.after(0, lambda: self.update_last_history_status("中断"))
                
                except Exception as e:
                    error_msg = f"播放错误: {str(e)[:20]}..."
                    self.root.after(0, lambda: self.status_var.set(error_msg))
                    self.root.after(0, lambda: self.add_to_play_history(key, file_path, "错误"))
                
                finally:
                    self.is_playing = False
                    self.root.after(0, lambda: self.stop_btn.config(state="disabled"))
                    if self.is_listening:
                        self.root.after(0, lambda: self.status_var.set("正在监听热键..."))
                    else:
                        self.root.after(0, lambda: self.status_var.set("就绪"))
            
            thread = threading.Thread(target=play_thread)
            thread.daemon = True
            thread.start()
            
        except Exception as e:
            self.status_var.set(f"播放错误: {e}")
            self.add_to_play_history(key, file_path, "错误")
            messagebox.showerror("播放错误", f"无法播放音频文件: {e}")
    
    def update_last_history_status(self, new_status):
        """更新最后一条播放记录的状态"""
        try:
            items = self.history_tree.get_children()
            if items:
                # 获取第一项（最新记录）的值
                first_item = items[0]
                values = list(self.history_tree.item(first_item, 'values'))
                if len(values) >= 4:
                    values[3] = new_status  # 更新状态列
                    self.history_tree.item(first_item, values=values)
            
            # 同时更新内部列表
            if self.play_history:
                self.play_history[0]['status'] = new_status
                
        except Exception as e:
            print(f"更新播放记录状态失败: {e}")
    
    def stop_audio(self):
        """停止音频播放 - 只停止播放，不关闭软件"""
        try:
            if self.is_playing:
                self.is_playing = False
                self.audio_simulator.stop()
                # 更新最后一条播放记录的状态为"中断"
                self.update_last_history_status("中断")
            
            self.stop_btn.config(state="disabled")
            
            if self.is_listening:
                self.status_var.set("正在监听热键...")
            else:
                self.status_var.set("就绪")
                
        except Exception as e:
            print(f"停止播放时出错: {e}")
            self.status_var.set("停止播放出错")
    
    def on_closing(self):
        """程序关闭时的清理"""
        self.stop_hotkey_listening()
        self.audio_simulator.stop()
        self.root.destroy()
    
    def run(self):
        """运行GUI程序"""
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.root.mainloop()

if __name__ == "__main__":
    try:
        app = AudioHotkeyGUI()
        app.run()
    except Exception as e:
        print(f"程序启动失败: {e}")
        import traceback
        traceback.print_exc()
